using System;

namespace LambdaGp.Core.Streaming
{
    public class GenomeLoader : GenomeLoaderBase
    {
        private const string Tokenizer = @"\d*\.\d*|\d*,\d*|\(|\)|[^\s()]*";

        public GenomeLoader(GenomeDefinition genomeDefinition)
            : base(genomeDefinition, Tokenizer)
        {
        }

        public GenomeLoader(Population population)
            : this(population.GenomeDefinition)
        {
        }

        protected override void LoadNodeFromTokenQueue(OperatorNode node)
        {
            string token = GetNextToken();
            if (token == "(")
            {
                // Expect Operator
                LoadFunctionFromTokenQueue(node);
            }
            else
            {
                // Expect terminal
                LoadTerminalFromToken(node, token);
            }
        }

        protected void LoadFunctionFromTokenQueue(OperatorNode node)
        {
            string token = GetNextToken();

            Operator nonTerminal = GenomeDefinition.GetFunctionByToken(token);
            if (nonTerminal.Arity == 0)
            {
                throw new InvalidOperationException(string.Format("Terminal '{0}' cannot be used as a Operator!", token));
            }

            node.Operator = nonTerminal;

            while (node.Children.Count < nonTerminal.Arity)
            {
                OperatorNode child = new OperatorNode();
                node.Children.Add(child);
                LoadNodeFromTokenQueue(child);
            }

            token = GetNextToken();
            if (token != ")")
            {
                throw new InvalidOperationException("Expected ')'!");
            }
        }

        protected void LoadTerminalFromToken(OperatorNode node, string token)
        {
            Operator terminal = GenomeDefinition.GetFunctionByToken(token);
            if (terminal.Arity > 0)
            {
                throw new InvalidOperationException(string.Format("Operator '{0}' cannot be used as a terminal!", token));
            }

            node.Operator = terminal;
        }
    }
}
